#!/usr/bin/python
#
# Copyright (c) 2011, Joshua Storck
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#  * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#  * The name of Joshua Storck may not be used to endorse or promote products
#       derived from this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL JOSHUA STORCK BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os
import re
import subprocess

def getNextTag(location, branch):
    '''
    Given the svn repository location for the branch and the actual
    branch version specified as a list of two integers, this function
    will determine the next micro based on the tags directory in the
    repository. If there is no micro in the tags directory, then it
    assumes this is the first micro for the branch. The return value is
    a list of three integers for major, minor, and micro
    '''
    cmd = ["svn", "list",
           re.sub("branches/[1-9]+\.[1-9]+$","tags", location) ]
    stdout = runCommandAndGetOutput(cmd)    
    if stdout == None:
        return None
    pattern = re.compile("([1-9]+)\.([1-9]+)\.([1-9]+)$")
    versions = []
    for line in stdout.split(os.linesep):
        matches = pattern.match(line)
        if not matches:
            continue
        versions.append([ int(matches.group(1)),
                          int(matches.group(2)),
                          int(matches.group(3)) ])
    if versions:
        #
        # Numerically sort the versions and only keep
        # those that match the branch
        #
        versions.sort()
        versions = [version for version in versions if (
            version[0] == branch[0] and
            version[1] == branch[1], versions)]
    if not versions:
        #
        # Assume this is the first micro
        #
        nextMicro = 1
    else:
        nextMicro = versions[len(versions)-1][2]
    nextTag = []
    nextTag.extend(branch)
    nextTag.append(nextMicro)
    return nextTag
    
def getBranch(topDir):
    '''
    Runs svn info on the working copy to find out what branch
    it has checked out from the repository. This will return a
    list of two integers for the major and minor versions for
    the branch
    '''
    cmd = ["svn", "info", topDir]
    stdout = runCommandAndGetOutput(cmd)
    if not stdout:
        return None
    pattern = re.compile("URL: (.*)")
    for line in stdout.split(os.linesep):
        matches = pattern.match(line)
        if not matches:
            continue
        location = matches.group(1)
        matches = re.match(".*branches/([1-9]+)\.([1-9]+)$", location)
        if not matches:
            print >> sys.stderr ("Invalid release branch {0}".format(
                location))
            return None
        return (location, [int(matches.group(1)),
                           int(matches.group(2))])
    return None

def findFiles(location, pattern):
    '''
    Finds all of the files beneath location that match the
    specified pattern
    '''
    return [os.path.join(root, file)
            for root, dirs, files in os.walk(location)
            for file in files if pattern.match(file)]

def runCommand(cmd):
    '''
    Runs the command and prints its output to stdout of
    this script and returns that output. If the command fails,
    it prints out the command's stderr and returns None
    '''
    stdout = runCommandAndGetOutput(cmd)
    if stdout == None:
        return None
    else:
        print(stdout)
        return stdout

def runCommandAndGetOutput(cmd):
    '''
    Runs the specified command and returns the command's output if
    successful. Otherwise, it prints out the command's stderr and
    return None.
    '''
    print("Running {0}".format(" ".join(cmd)))
    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    (stdout, stderr) = process.communicate()
    if process.returncode:
        print(stderr)
        return None
    return stdout

def runJar(topDir, version):
    '''
    Creates a jar file, inluding the class files and the SPI configuration
    file for the annotation processor
    '''
    binDir = os.path.join(topDir, "bin")
    jarFile = os.path.join(binDir, "spicfg-{0}.jar".format(
        ".".join(map(str, version))))
    metaInfFile = os.path.join("META-INF",
                               "services",
                               "javax.annotation.processing.Processor")
    cmd = ["jar", "cvf", jarFile,
           "-C", binDir, "com",
           metaInfFile]
    return runCommand(cmd)                

def runJavadoc(topDir):
    '''
    Generates javadoc on all of the java files in the source directory
    '''
    files = findFiles(os.path.join(topDir, "src"),
                      re.compile(".*\.java$"))
    cmd = ["javadoc",
           "-public",
           "-notimestamp",
           "-link", "http://java.sun.com/javase/6/docs/api/",
           "-d", os.path.join(topDir, "doc")]
    cmd.extend(files)
    return runCommand(cmd)

def main():
    #
    # Find out where the top level directory is relative to this script
    #
    # top/python/thisScript
    #
    pythonDir = os.path.dirname(os.path.abspath(__file__))
    topDir = os.path.dirname(pythonDir)
    #
    # Figure out what branch this is
    #
    (location, branch) = getBranch(topDir)
    if not branch:
        print >> stderr ("Unable to determine branch, " +
                         "perhaps your working copy " +
                         "is not a branch?")
        return 1
    else:
        print("Working copy is branch {0}".format(
            ".".join(map(str,branch))))
    #
    # Get the next tag from SVN based on the branch
    #
    tag = getNextTag(location, branch)
    if not tag:
        print >> stderr ("Unable to determine the next " +
                         "micro version for branch " +
                         ".".join(map(str,branch)))
        return 1
    else:
        print("Target release is {0}".format(
            ".".join(map(str,tag))))
    #
    # Generate javadoc
    #
    if not runJavadoc(topDir):
        return 1
    #
    # Generate the jar
    #
    elif not runJar(topDir, tag):
        return 1
    return 0

if __name__ == "__main__":
    main()
